/*
    Project:        Home Power Control
    File:           HPCClient.cpp
    Author:         christian@karunamusic.at
    Date:           01.2015
    Description:    The HPC Client.
 */

#include <QSettings>
#include <QDomNodeList>
#include <QMapIterator>
#include <QTextStream>
#include <QFile>
#include <iostream>
#include <unistd.h>
#include <wiringPi.h>
#include "HPCLogger.h"
#include "Sensors/Misc/HPCButton.h"
#include "Sensors/Weather/HPCLight.h"
#include "Sensors/Weather/HPCAnemometer.h"
#include "Sensors/Weather/HPCRainGauge.h"
#include "Sensors/Weather/HPCWindVane.h"
#include "Sensors/Weather/HPCDHT.h"
#include "Sensors/Weather/HPCBarometer.h"
#include "HPCRequest.h"
#include "HPCClient.h"

HPCClient::HPCClient(int& argc, char** argv)
    : HPCApplication(argc,argv,"Client"), m_hpcServer(), m_hpcPort(0), m_Socket(0), m_Connected(false)
{
    if ( wiringPiSetup() == -1 )
    {
        THROW( "wiringPiSetup() failed!" );
    }
}

HPCClient::~HPCClient()
{
    QMapIterator<QString, HPCSensor*> i(m_Sensors);
    while ( i.hasNext() )
    {
        i.next();
        LOG( info,"Remove sensor " << i.key() );
        delete i.value();
    }
    m_Sensors.clear();
}

void HPCClient::Run()
{
    HPCApplication::Run();

    LOG( info, "Connecting to server " << m_hpcServer << ":" << m_hpcPort );
    m_Socket = new QTcpSocket(this);
    connect( m_Socket,SIGNAL(connected()),         this,SLOT(connected()) );
    connect( m_Socket,SIGNAL(disconnected()),      this,SLOT(disconnected()) );
    connect( m_Socket,SIGNAL(bytesWritten(qint64)),this,SLOT(bytesWritten(qint64)) );
    connect( m_Socket,SIGNAL(readyRead()),         this,SLOT(readyRead()) );

    while ( m_Run )
    {
        processEvents();

        if ( !m_Connected )
        {
            m_Socket->connectToHost(m_hpcServer,m_hpcPort);
            if ( !m_Socket->waitForConnected(5000) )
            {
                LOG(fatal,"could not connect to server " << m_hpcServer << ":" << m_hpcPort);
                //THROW( m_Socket->errorString() );
            }
        }

        if ( m_Sensors.size() )
        {
            QMapIterator<QString, HPCSensor*> i(m_Sensors);
            while ( i.hasNext() )
            {
                i.next();
                i.value()->Measure();
                processEvents();
            }
        }
    }

    quit();
}

void HPCClient::command( QString cmd )
{
    HPCApplication::command( cmd );
    QStringList argv = cmd.split(" ",QString::SkipEmptyParts);
    QTextStream cout(stdout);

    if ( m_Run )
    {
        if ( argv[0] == "exit" || argv[0] == "quit" || argv[0] == "loglevel" || argv[0] == "help" )
        {
            // do nothing - handled in HPCApplication!
        }
        else if ( argv[0] == "ls" || argv[0] == "list" )
        {
            QMapIterator<QString, HPCSensor*> i(m_Sensors);
            while ( i.hasNext() )
            {
                i.next();
                cout << "Sensor " << i.value()->asString() << endl;
            }
        }
        else if ( argv[0] == "read" )
        {
            if ( argv.size() != 2 )
            {
                cout << "missing sensor name!" << endl;
                LOG(warning,"missing sensor name!");
            }
            else
            {
                if ( argv[1] == "all" )
                {
                    QMapIterator<QString, HPCSensor*> i(m_Sensors);
                    while ( i.hasNext() )
                    {
                        i.next();
                        HPCValue v = i.value()->Value();
                        if ( v.isValid() )
                        {
                            cout << i.key() << ": " << v.V().toString() << " " << i.value()->MeasureUnit() << endl;
                        }
                        else
                        {
                            cout << i.key() << ": not valid." << endl;
                        }
                    }
                }
                else
                {
                    if ( m_Sensors.contains(argv[1]) )
                    {
                        HPCValue v = m_Sensors[argv[1]]->Value();
                        if ( v.isValid() )
                        {
                            cout << argv[1] << ": " << v.V().toString() << " " << m_Sensors[argv[1]]->MeasureUnit() << endl;
                        }
                        else
                        {
                            cout << argv[1] << ": not valid." << endl;
                        }
                    }
                    else
                    {
                        cout << "cannot read undefined sensor '" << argv[1] << "'" << endl;
                        LOG(warning,"cannot read undefined sensor '" << argv[1] << "'");
                    }
                }
            }
        }
        else
        {
            cout << "invalid command '" << cmd << "'!" << endl;
            LOG(warning,"Invalid command recieved: " << cmd);
            cliHelp();
        }
    }
}

void HPCClient::ValueChanged( HPCSensor* sender, HPCSensorValue& val )
{
    LOG( debug, "Sensor " << sender->Name() << " changed: " << val.V().toString() );
    HPCRequest req(valueChanged);

    req.Stream() << sender->Name() << val.V();
    int res = m_Socket->write(req);
    if ( res != -1 )
    {
        LOG(debug, QString::number(res) << " bytes streamed to server.")
    }
    else
    {
        LOG(warning, "cannot write to server!")
    }
}

void HPCClient::ValueAlarmed( HPCSensor* sender, HPCSensorValue& val, bool gt, unsigned int ndx )
{
    LOG( debug, "Sensor " << sender->Name() << " alarmed: " << val.V().toString() << ", Max: " << gt << ", index: " << ndx );
    HPCRequest req(valueAlarmed);

    req.Stream() << sender->Name() << val.V() << gt << ndx;
    int res = m_Socket->write(req);
    if ( res != -1 )
    {
        LOG(debug, QString::number(res) << " bytes streamed to server.")
    }
    else
    {
        LOG(warning, "cannot write to server!")
    }
}

void HPCClient::connected()
{
    LOG(info,"client connected.");
    m_Connected = true;
    HPCRequest req(config);

    req.Stream() << m_Name;
    m_Socket->write(req);
}

void HPCClient::disconnected()
{
    LOG(info,"client disconnected.");
    m_Connected = false;
}

void HPCClient::bytesWritten(qint64 bytes)
{
    LOG(debug,QString::number(bytes) << " bytes sent to server.");
}

void HPCClient::readyRead()
{
    QByteArray ba = m_Socket->readAll();
    HPCRequest req(ba);
    switch ( req.Type() )
    {
        case config:
        {
            LOG(info,"got configuration from server.");
            req.Stream() >> m_Sensors;
            QMapIterator<QString, HPCSensor*> i(m_Sensors);
            while ( i.hasNext() )
            {
                i.next();
                connect( i.value(), SIGNAL(changed     (HPCSensor*, HPCSensorValue&)),
                         this     , SLOT  (ValueChanged(HPCSensor*, HPCSensorValue&)) );
                connect( i.value(), SIGNAL(alarmed     (HPCSensor*, HPCSensorValue&, bool, unsigned int)),
                         this     , SLOT  (ValueAlarmed(HPCSensor*, HPCSensorValue&, bool, unsigned int)) );
            }
        }
        break;
        default:
        {
            LOG(error,"invalid request type "<< (unsigned int)req.Type() << " recieved!");
        }
        break;
    }
}

void HPCClient::readConfig()
{
    HPCApplication::readConfig();

    QSettings cfg( m_ConfigFile, QSettings::IniFormat );

    m_Name      = cfg.value("connection/clientname").toString() ;
    m_hpcServer = cfg.value("connection/hpcserver").toString() ;
    m_hpcPort   = cfg.value("connection/hpcport").toUInt();
}

void HPCClient::cliHelp( QString topic )
{
    HPCApplication::cliHelp(topic);
    if ( topic == "loglevel" )
        return;

    QTextStream cout(stdout);
    if ( topic == "" )
    {
        cout << "ls, list\t\tlist sensors/actors." << endl
             << "read <sensor>|all\tread the value of <sensor>." << endl;
    }
    else if ( topic == "ls" || topic == "list" )
    {
        cout << "ls, list\t\tlist sensors/actors." << endl
             << endl
             << "list all configured sensors and actors on this device with their attributes." << endl
             << endl;
    }
    else if ( topic == "read" )
    {
        cout << "read <sensor>|all\tread the value of <sensor>." << endl
             << endl
             << "read the value of sensor <sensor>, where <sensor> is the name of the sensor or 'all' to read all sensors." << endl
             << endl
             << "find out the name of the sensors using the 'list' command!"
             << endl;
    }
}

QDataStream& operator>> ( QDataStream& is, QMap<QString,HPCSensor*>& smap )
{
    int sensors;
    is >> sensors;
    LOG(info,"get " << sensors << " sensors.");

    for ( int i = 0; i < sensors; i++ )
    {
        quint16 t;
        is >> t;
        HPCSensor::TSensorType type = (HPCSensor::TSensorType)t;
        if ( type == HPCSensor::TSensorType::undefined )
            continue;

        QString name;
        quint32 id;
        quint8  pin;
        QString munit;
        HPCSensor *sensor = 0;

        is >> name >> id >> pin >> munit;
        switch ( type )
        {
            case HPCSensor::TSensorType::HPCButton:
            {
                sensor = new HPCButton(name,id,pin);
                is >> sensor->m_Value;
            }
            break;
            case HPCSensor::TSensorType::HPCLight:
            {
                sensor = new HPCLight(name,id,pin);
                is >> sensor->m_Value;
            }
            break;
            case HPCSensor::TSensorType::HPCAnemometer:
            {
                quint16 interval;
                sensor = new HPCAnemometer(name,id,pin,120);
                is >> sensor->m_Value >> interval;
                dynamic_cast<HPCAnemometer*>(sensor)->Interval(interval);
            }
            break;
            case HPCSensor::TSensorType::HPCRainGauge:
            {
                quint16 interval;
                sensor = new HPCRainGauge(name,id,pin,120);
                is >> sensor->m_Value >> interval;
                dynamic_cast<HPCRainGauge*>(sensor)->Interval(interval);
            }
            break;
            case HPCSensor::TSensorType::HPCWindVane:
            {
                quint8 ch;
                sensor = new HPCWindVane(name,id,0,pin);
                is >> sensor->m_Value >> ch;
                dynamic_cast<HPCWindVane*>(sensor)->Channel(ch);
            }
            break;
            case HPCSensor::TSensorType::HPCHumidity22:
            {
                sensor = new HPCHumidity22(name,id,pin);
                is >> sensor->m_Value;
            }
            break;
            case HPCSensor::TSensorType::HPCTemperature22:
            {
                sensor = new HPCTemperature22(name,id,pin);
                is >> sensor->m_Value;
            }
            break;
            case HPCSensor::TSensorType::HPCBarometer:
            {
                quint16 tmp;
                sensor = new HPCBarometer(name,id,pin);
                is >> sensor->m_Value;
                is >> tmp; // ???
            }
            break;
            default:
            {
                LOG( warning,"invalid sensortype " << (unsigned int)type );
            }
        }

        if ( sensor )
        {
            sensor->MeasureUnit(munit);

            LOG( info, sensor->asString() << " created.");
            smap.insert(sensor->Name(),sensor);
        }
    }

    return is;
}
